/**
 * 
 */
package PagedFile;

import java.io.*;
import java.nio.channels.FileChannel;
import java.util.*;

/**
 * @author Neil
 *
 */
public class PF_Manager implements PF_Manager_Interface {

	private File dbDirectory;
	private Map<String, PF_FileHandle> openFiles = new HashMap<String, PF_FileHandle>();
	public BF_Manager bfm;
	
	/*
	 * There'll be only one PF_Manager in a miniDB system.
	 */
	public PF_Manager(String dirname) {
		dbDirectory = new File(dirname);
		if (!dbDirectory.exists() && !dbDirectory.mkdir())
			throw new RuntimeException ("Cannot Create Dir: " + dirname);
		bfm = new BF_Manager();
	}
	
	/* (non-Javadoc)
	 * @see PagedFile.PF_Manager_Interface#AllocateBlock()
	 */
	@Override
	public Buffer AllocateBlock(Block blk, FileChannel fc) {
		return bfm.pin(blk, fc);
	}

	/* (non-Javadoc)
	 * @see PagedFile.PF_Manager_Interface#CreateFile(java.lang.String)
	 * Create a File. Add a file header to the first block.
	 */
	@Override
	public PF_FileHandle CreateFile(String filename) throws IOException, PF_Abort_Exception{
		File f = new File(dbDirectory, filename);
		if (f.exists())
			throw new PF_Abort_Exception("File Exists");
		f.createNewFile();
		
		return getFileHandle(f);
	}

	private PF_FileHandle getFileHandle(File f) throws IOException{
		/* Check if the file has already been opened */
		PF_FileHandle fh = openFiles.get(f.getName());
		if ( fh != null ) {
			fh.openCnt++;
			return fh;
		}
		
		/* If it's not open yet, a new FileHandle will be established. */
		RandomAccessFile raf = new RandomAccessFile(f, "rws");
		FileChannel fc = raf.getChannel();
		
		Buffer headBuffer = bfm.pin(new Block(f.getName(), 0), fc);
		PF_FileHead fhead = null;
		
		if ( raf.length()==0 ) { //It's an empty file
			/* Establish the head page, and write it to the file. */
			fhead = new PF_FileHead();
			headBuffer.mark(this.hashCode());
			fhead.put_into_ByteBuffer(headBuffer.contents);
			headBuffer.write();			
		} else { //The file has already been initialized.
			/* Read the head page from the file. */
			headBuffer.read();
			fhead = new PF_FileHead(headBuffer.contents);
		}
		bfm.unpin(headBuffer);
		
		fh = new PF_FileHandle(f.getName(), fc, fhead, bfm);
		openFiles.put(f.getName(), fh);
		return fh;
	}
	
	/* (non-Javadoc)
	 * @see PagedFile.PF_Manager_Interface#CloseFile(PagedFile.PF_FileHandle)
	 */
	@Override
	public void CloseFile(PF_FileHandle fh) {
		if (fh.openCnt==0)
			throw new PF_Abort_Exception("Handle Not Open");
		--fh.openCnt;

		FileChannel fc = fh.fc;	
		if ( fc == null )
			throw new PF_Abort_Exception("Fatal");
		
		bfm.flushAll(fc);
		bfm.unpinAll(fc);
		
		/* Store the file head into the file. */
		Buffer headBuffer = bfm.pin(new Block(fh.filename, 0), fc);
		headBuffer.mark(this.hashCode());
		fh.fhead.put_into_ByteBuffer(headBuffer.contents);
		headBuffer.write();
		bfm.unpin(headBuffer);
		
		if (fh.openCnt==0) {
			openFiles.remove(fh.filename);
			try {
				fh.fc.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/* (non-Javadoc)
	 * @see PagedFile.PF_Manager_Interface#DestroyFile(java.lang.String)
	 */
	@Override
	public void DestroyFile(String filename) {
		PF_FileHandle fh = openFiles.get(filename);
		FileChannel fc = fh.fc;
		if ( fc == null ) 
			throw new PF_Abort_Exception("Fatal");
		bfm.unpinAll(fc);
		fh.openCnt=0;
		try {
			fc.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		new File(dbDirectory, filename).delete();
		
		openFiles.remove(filename);
	}

	/* (non-Javadoc)
	 * @see PagedFile.PF_Manager_Interface#DisposeBlock(java.nio.ByteBuffer)
	 */
	@Override
	public void DisposeBlock(Buffer buf) {
		bfm.unpin(buf);
	}

	/* (non-Javadoc)
	 * @see PagedFile.PF_Manager_Interface#OpenFile(java.lang.String)
	 */
	@Override
	public PF_FileHandle OpenFile(String filename) throws IOException {
		File f = new File(dbDirectory, filename);
		if (!f.exists())
			throw new PF_Abort_Exception("File Not Exists");
		
		return getFileHandle(f);
	}
	
	public boolean CheckOpen(PF_FileHandle fh) {
		return fh.openCnt>0;
	}
	
}
